package fons.navigator.views.river;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.activities.PActivity;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.util.PBounds;
import fons.model.ontology.Context;
import fons.model.ontology.Dimension;
import fons.model.ontology.SelectionDefinition;
import fons.model.ontology.SelectionUpdateListener;
import fons.navigator.util.GeomUtils;
import fons.navigator.util.piccolo.ContextMenu;
import fons.navigator.util.piccolo.PButton;
import fons.navigator.util.piccolo.PNodes;

public class VisualRiver extends PNode implements SelectionUpdateListener {
	private static final long serialVersionUID = 1L;
	
	public final static float LANE_SPACING = 80;
	public final static float RIVER_SPACING = 20;
	
	@SuppressWarnings("unused")
	private final PButton percentAbsoluteButton;
	private final RiverView riverView;
	private final Set<Dimension> streamParents;
	private final Set<Dimension> valueDimensions;
	
	private final Context context;
	
	private final Stream mainStream;
	
	private static class StreamListItem {
		final public Stream stream;
		final public List<Stream> children = Lists.newArrayList();
		
		public StreamListItem (Stream stream) {
			this.stream = stream;
		}
	}
	
	private final List<List<StreamListItem>> streams = Lists.newArrayList();
	
	public void rearrangeLanes () {
		double xOffset = 0;
		
		for (List<StreamListItem> items: streams) {
			double laneWidth = 0;
			for (StreamListItem item: items) {
				if (item.stream.getFullBoundsReference().getWidth() > laneWidth) {
					laneWidth = item.stream.getFullBoundsReference().getWidth();
				}
			}
			
			for(StreamListItem item: items) {
				item.stream.setOffset(xOffset, item.stream.getYOffset());
			}

			xOffset += laneWidth + LANE_SPACING;
		}
	}
	
	public void reorder (int depth) {
		if (depth == 0) return;
		
		double totalHeight = 0;
		for (StreamListItem parentItem: streams.get(depth-1)) {
			
			double childrenHeight = RIVER_SPACING;
			for (Stream stream: parentItem.children) {
				childrenHeight += stream.getGlobalFullBounds().height + RIVER_SPACING;
			}
			
			Collections.sort(parentItem.children);
			
			double height = parentItem.stream.getGlobalFullBounds().height + RIVER_SPACING;
			if (height > childrenHeight) totalHeight += height;
			else totalHeight += childrenHeight;
		}
		
		double height = 0;
		for (StreamListItem parentItem: streams.get(depth-1)) {
			final Stream parent = parentItem.stream;
			final double parentHeight = parent.getGlobalFullBounds().height + RIVER_SPACING;
			
			double childrenHeight = RIVER_SPACING;
			for (Stream stream: parentItem.children) {
				childrenHeight += stream.getGlobalFullBounds().height + RIVER_SPACING;
			}			
			
			parent.setOffset(parent.getXOffset(), 0);
			PNodes.moveTo(parent, parent.getX(), - totalHeight + height
					+ (childrenHeight > parentHeight ? childrenHeight : parentHeight)/2 - parentHeight/2);

			double childHeight = RIVER_SPACING;
			for (Stream stream: parentItem.children) {
				stream.setOffset(stream.getXOffset(), 0);
				PNodes.moveTo(stream, stream.getX(), - totalHeight + height + childHeight);
				childHeight += stream.getGlobalFullBounds().height + RIVER_SPACING;
			}
			
			if (childrenHeight > parentHeight) height += childrenHeight;
			else height += parentHeight;
		}
	}
		
	public VisualRiver (final RiverView view, final Context context,
			final Set<Dimension> valueDimensions, final Set<Dimension> streamParents) {
		
		this.riverView = view;
		this.context = context;
		this.valueDimensions = valueDimensions;
		this.streamParents = streamParents;
		this.mainStream = new Stream(this, this.valueDimensions, this.streamParents);
		this.streams.add(Lists.newArrayList(new StreamListItem(mainStream)));
		getContext().getStandardSelection().addSelectionUpdateListener(this);		
		addChild(mainStream);
		mainStream.redraw();
		
		ContextMenu.addTo(mainStream, new LaneMenu(this, view.getCamera()));
		OptionOverlay.addTo(mainStream, new OptionOverlay(this, view.getCamera()));
		
		this.addInputEventListener(new PBasicInputEventHandler() {
			
			@Override
			public void mouseClicked(PInputEvent event) {
				super.mouseClicked(event);
				
				if (!(event.getPickedNode() instanceof Stream)) return;
				final Stream stream = (Stream) event.getPickedNode();
				
				if (event.isLeftMouseButton()) {
					if (event.isControlDown()) {
						PBounds boundRect = stream.getFullBounds();
						boundRect = (PBounds)view.getCamera().globalToLocal(boundRect);
						view.getCamera().animateViewToCenterBounds(GeomUtils.growRectByRelativeSize(boundRect, .15, .15, .15, .15), true, 1000);
						return;
					}
				} 
			}
		});
				
		view.getVisualCanvas().getModeButtonsPanel()
		.addButton(percentAbsoluteButton = new PButton("Test"), new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				super.mouseClicked(event);
				
			}
		});
		
		view.getVisualCanvas().addInputEventListener(new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				super.mouseClicked(event);
			}
		});
		
		PBounds boundRect = this.getFullBounds();
		boundRect = (PBounds)view.getCamera().globalToLocal(boundRect);
		view.getCamera().animateViewToCenterBounds(GeomUtils.growRectByRelativeSize(boundRect, .55, .55, .55, .55), true, 0);
	}
	
	public void rescale () {
		@SuppressWarnings("unused")
		int lane = 0;
		for (List<StreamListItem> items: streams) {
			for (StreamListItem item: items) {
				@SuppressWarnings("unused")
				final Stream stream = item.stream;
				
			}
			lane++;
		}
	}

	public void addStream(Stream parentStream, Stream subStream) {
		int index = getStreamIndex(parentStream) + 1;
		
		ContextMenu.addTo(subStream, new LaneMenu(this, riverView.getCamera()));
		OptionOverlay.addTo(subStream, new OptionOverlay(this, riverView.getCamera()));
		
		getStreamListItem(parentStream).children.add(subStream);
		this.addChild(subStream);
		subStream.moveToBack();
		
		if (streams.size() > index) {
			streams.get(index).add(new StreamListItem(subStream));
			Collections.sort(streams.get(index), new Comparator<StreamListItem>() {
				@Override
				public int compare(StreamListItem o1, StreamListItem o2) {
					return Double.compare(o1.stream.getGlobalBounds().getMinY(), o2.stream.getGlobalBounds().getMinY());
				}
			});
			
			int insertedAt = 0;
			for (int i = 0; i < streams.get(index).size(); ++i) {
				if (streams.get(index).get(i).stream == subStream)
					insertedAt = i;
			}
			insertStreamAt(streams.get(index), insertedAt, subStream);
			
		} else {
			streams.add(Lists.newArrayList(new StreamListItem(subStream)));
		}
	}
	
	private StreamListItem getStreamListItem(Stream stream) {
		for (StreamListItem item: streams.get(getStreamIndex(stream))) {
			if (stream.isPartOfStream(item.stream)) return item;
		}
		
		throw new IllegalArgumentException("Something went wrong!");
	}

	private void insertStreamAt(final List<StreamListItem> streamList, final int index, final Stream subStream) {
		PActivity activity = new PActivity(600) {
			private double distanceAbove;
			private double distanceBelow;
			
			@Override
			protected void activityStarted() {
				if (index-1 >= 0) {
					final Stream above = streamList.get(index-1).stream.getRootStream();

					distanceAbove = subStream.getGlobalFullBounds().getMinY() - above.getGlobalFullBounds().getMaxY();
					if (distanceAbove < RIVER_SPACING) {
						distanceAbove = RIVER_SPACING - distanceAbove;
					} else 
						distanceAbove = 0;
				}
				
				if (index+1 < streamList.size()) {
					final Stream below = streamList.get(index+1).stream.getRootStream();

					distanceBelow = below.getGlobalFullBounds().getMinY() - subStream.getGlobalFullBounds().getMaxY();
					if (distanceBelow < RIVER_SPACING)
						distanceBelow = RIVER_SPACING - distanceBelow;
					else 
						distanceBelow = 0;
				}
				
				if (distanceAbove == 0 && distanceBelow == 0) {
					getActivityScheduler().removeActivity(this);
				}
			}
			
			private double lastTime;
			@Override
			protected void activityStep(long elapsedTime) {
				super.activityStep(elapsedTime);
				
				if (index-1 >= 0) {
					for (int i = index-1; i >= 0; --i) {
						final Stream stream = streamList.get(i).stream;
						PNodes.moveTo(stream, stream.getX(), stream.getY() - distanceAbove * (elapsedTime-lastTime)/getDuration());
						stream.invalidateFullBounds();
					}
				}

				if (index+1 < streamList.size()) {
					for (int i = index+1; i < streamList.size(); ++i) {
						final Stream stream = streamList.get(i).stream;
						PNodes.moveTo(stream, stream.getX(), stream.getY() + distanceBelow * (elapsedTime-lastTime)/getDuration());
						stream.invalidateFullBounds();
					}
				}
				
				lastTime = elapsedTime;
			}
		};
		getRoot().getActivityScheduler().addActivity(activity, true);
	}

	public int getStreamIndex (Stream stream) {
		int index = 0;
		for (final List<StreamListItem> streamList: streams) {
			for (final StreamListItem candidate: streamList) {
				if (stream.isPartOfStream(candidate.stream)) return index;
			}
			index ++;
		}
		//throw new IllegalArgumentException("Stream is not part of this visualRiver!");
		return 0;
	}

	public Context getContext() {
		return this.context;
	}
	
	public RiverView getRiverView() {
		return this.riverView;
	}
	
	@Override
	public void dispose() {
	
	}
	
	@Override
	public void beforeSelectionChanged() {
	}

	@Override
	public void afterSelectionChanged() {
		final Set<Dimension> selected = getSelectedDimensions();
		final Set<Dimension> available = getAvailableDimensions();
		
		for (Dimension d: Sets.difference(available, selected)) {
			removeCurrentsWithCompositeDimension(d);
		}
		
		for (Dimension d: selected) {
			addCurrentsWithCompositeDimension(d);
		}
		
		this.redraw();
	}
	
	public Set<Dimension> getSelectedDimensions () {
		return context.getStandardSelection().get("RIVER-SELECTION");
	}
	
	public Set<Dimension> getAvailableDimensions () {
		return context.getStandardSelection().get("RIVER-AVAILABLE");
	}

	private void addCurrentsWithCompositeDimension(Dimension d) {
		for (List<StreamListItem> items: streams) {
			for (StreamListItem item: items) {
				item.stream.addCurrent(d);
			}
		}
	}

	private void removeCurrentsWithCompositeDimension(Dimension d) {
		for (List<StreamListItem> items: streams) {
			for (StreamListItem item: items) {
				item.stream.removeCurrent(d);
			}
		}
	}

	@Override
	public void selectedSelection(SelectionDefinition definition) {
		
	}

	public void redraw() {
		for (List<StreamListItem> items: streams) {
			for (StreamListItem item: items) {
				item.stream.redraw();
			}
		}
	}

	public void removeNodesFromCamera() {
	}

	public void addNodesToCamera() {
	}

	public void addAvailableDimensions(Set<Dimension> dimensions) {
		context.getStandardSelection().select("RIVER-AVAILABLE", dimensions);
		context.getStandardSelection().select("RIVER-SELECTION", dimensions);
	}

	public void removeLane(final int lane) {
		double maxWidth = 0;
		
		if (lane < streams.size()) {
			for (StreamListItem item: streams.get(lane)) {
				if (item.stream.getWidth() > maxWidth) maxWidth = item.stream.getWidth();
				removeChild(item.stream);
			}
			streams.remove(lane);
		}

		final double offset = maxWidth;
		PActivity activity = new PActivity (500) {
			private long lastTime;
			@Override
			protected void activityStep(long elapsedTime) {
				super.activityStep(elapsedTime);
				double stepTime = elapsedTime - lastTime;

				for (int i = lane; i < streams.size(); ++i) {
					for (StreamListItem item: streams.get(i)) {
						item.stream.setX(item.stream.getX() - (stepTime/getDuration()) * (LANE_SPACING + offset));
					}
				}
				
				
				lastTime = elapsedTime;
			}
		};
		getRoot().addActivity(activity);
		
		this.redraw();
	}
	
	public List<Stream> getStreams (int lane) {
		List<Stream> tmp = Lists.newArrayListWithCapacity(lane < streams.size() ? streams.get(lane).size() : 0);
		for (StreamListItem item: streams.get(lane)) {
			tmp.add(item.stream);
		}
		return tmp;		
	}

	public void showAbsoluteValues() {
		mainStream.showAbsoluteValues();
		mainStream.redraw();
	}
}
